#TODO: unittest  for all
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any, Callable

from bcat.error import InvalidArgumentError
from bcat.stream.io_wrapper import BinaryReader, TextReader, BinaryWriter, TextWriter
from bcat.process.process import Process
from bcat.process.process_info import ProcessInfo




class IVisitor(ABC):
    @abstractmethod
    def visit(self, data: Any) -> bool:
        pass

#visitor wrapper for function of python
class VisitorWrapper(IVisitor):
    def __init__(self, cb: Callable[[Any], bool]):
        self.__cb = cb
    def visit(self, data: Any) -> bool:
        return self.__cb(data)






class Visitable(ABC):
    def __init__(self, visitor: IVisitor):
        self.__visitor = visitor

    def visitor(self):
        return self.__visitor






class Follower(object):
    class _Base(Visitable): 
        def __init__(self, process: Process, visitor: IVisitor):
            super().__init__(visitor)
            self.__process = process

        def process(self) -> Process:
            return self.__process

        def _call_visitor(self, reader: Any) -> bool:
            if self.visitor() is not None:
                return self.visitor().visit((reader, self.process())) 
            return False

        def _create_stream(self) -> Any:
            raise NotImplementedError("_create-stream not implemented")

        def as_text(self) -> bool:
            reader = self._create_stream().text_reader()
            return self._call_visitor(reader)
        def as_binary(self) -> bool:
            reader = self._create_stream().binary_reader()
            return self._call_visitor(reader)

        def __repr__(self):
            return f"Follower._Base(process={self.process()}, visitor:{self.visitor()})"

    class Stdout(_Base):
        @staticmethod 
        def from_callable(process: Process, cb: Callable[[Any],bool]) -> Follower.Stdout:
            return Follower.Stdout(process, VisitorWrapper(cb))
        def __init__(self, process: Process, visitor: IVisitor):
            super().__init__(process, visitor)

        def _create_stream(self) -> Any:
            return ProcessInfo.Stdout(self.process())
        def __repr__(self):
            return f"Follower.Stdout(process={self.process()}, visitor:{self.visitor()})"
            
    class Stderr(_Base):
        @staticmethod 
        def from_callable(process: Process, cb: Callable[[Any],bool]) -> Follower.Stderr:
            return Follower.Stderr(process, VisitorWrapper(cb))

        def __init__(self, process: Process, visitor: IVisitor):
            super().__init__(process, visitor)

        def _create_stream(self) -> Any:
            return ProcessInfo.Stderr(self.process())
        def __repr__(self):
            return f"Follower.Stderr(process={self.process()}, visitor:{self.visitor()})"

    class FetchAll(_Base):
        @staticmethod 
        def from_callable(process: Process, cb: Callable[[Any],bool]) -> Follower.FetchAll:
            return Follower.FetchAll(process, VisitorWrapper(cb))

        def __init__(self, process: Process, visitor: IVisitor, timeout: int|None = None):
            super().__init__(process, visitor)
            self.__timeout = timeout

        def as_text(self) -> bool:
            if not self.process().is_terminated():
                (outs, errs) = self.process().communicate(timeout=self.__timeout)
                if isinstance(outs, str) and isinstance(errs, str):
                    self.visitor().visit((outs, errs))
                else:
                    raise InvalidArgumentError.no_args(f"text attribute of not set before executing, {self.process()}")
            return True

        def as_binary(self) -> bool:
            if not self.process().is_terminated():
                (outs, errs) = self.process().communicate(timeout=self.__timeout)
                if isinstance(outs, bytes) and isinstance(errs, bytes):
                    self.visitor().visit((outs, errs))
                else:
                    raise InvalidArgumentError.no_args(f"text attribute of Must be set before executing, {self.process()}")

            return True
        def __repr__(self):
            return f"Follower.FetchAll(process={self.process()}, visitor:{self.visitor()})"


import unittest
from bcat.process.executables import Ping
from bcat.process.executable_wrappers import ExecutableWrapper
class TestFlollower(unittest.TestCase):
    def test_fetch_all(self):
        def get_all(args: Any) -> bool:
            outs, errs = args
            print(f"outs:{outs}")
            print(f"errs:{errs}")
            return True

        process = Ping.local_host(1).stdout().stderr().text().execute()
        Follower.FetchAll.from_callable(process, get_all).as_text()

        process = Ping.local_host(1).stdout().stderr().text(False).execute()
        Follower.FetchAll.from_callable(process, get_all).as_binary()
        
    def test_stdout_text(self):
        def print_line(data: Any) -> bool:
            reader, process = data
            if reader.closed() or process.is_terminated():
                return False
            while True:
                line = reader.read_line()
                if line is None:
                    break
                else:
                    print("follow by test_text: ", line)
            return True
        #process = Ping.local_host(1).stdout().text().execute()
        process = ExecutableWrapper.Text(ExecutableWrapper.Stdout(Ping.local_host(1))).execute()
        Follower.Stdout.from_callable(process, print_line).as_text()

    def test_stdout_binary(self):
        def print_block(data: Any) -> bool:
            reader, process = data
            if reader.closed() or process.is_terminated():
                    return False
            while True:
                block = reader.read(50)
                if block is None:
                    break
                else:
                    print(block.decode())
            return True
        #process = Ping.local_host(1).stdout().execute()
        process = ExecutableWrapper.Stdout(Ping.local_host(1)).execute()
        Follower.Stdout.from_callable(process, print_block).as_binary()
        

#TODO: ????
class Emitter(Visitable):
    def __init__(self, process: Process, visitor: IVisitor):
        super().__init__(visitor)
        self.__process = process

    def stdin_as_text(self) -> int: 
        return 1
        #return self._write_as_text(ProcessInfo.Stdin(self.__process).text_writer())


